import { _decorator, animation, CapsuleCharacterController, CharacterControllerContact, clamp, Collider, geometry, JsonAsset, Node, ParticleSystem, PhysicsSystem, Prefab, randomRange, RigidBody, toDegree, toRadian, tween, v3, Vec3 } from 'cc';
import { GamerController } from './GamerController';
import { GameUI } from './GameUI';
import { nUtils } from '../Navimesh/libs/nUtils';
import { InGameManager } from './Managers/InGameManager';
import { Builder } from '../Navimesh/libs/Builder';
import { NavMesh } from '../Navimesh/libs/NavMesh';
import { BodyTrigger } from './BodyTrigger';
import { ControlUI } from './UIs/ControlUI';
import { PoolManager } from '../../Scripts/Framework/Managers/PoolManager';
import { PlayerController } from './PlayerController';
import { Gun } from './Items/Gun';
import { Weapon } from './Items/Weapon';
import { AudioManager } from './Managers/AudioManager';
const { ccclass, property } = _decorator;

@ccclass('RobotController')
export class RobotController extends GamerController {

    @property(Node)
    minigun: Node;

    @property(Node)
    minigunFire: Node;

    @property(Node)
    chargeGun: Node;

    @property(Node)
    chargeParticle: Node;

    @property(Node)
    patrolPoints: Node;

    @property(ParticleSystem)
    eye: ParticleSystem;

    @property(Prefab)
    shootParticle: Prefab;

    @property(Prefab)
    shootParticle2: Prefab;

    @property(Prefab)
    bullet: Prefab;

    @property(Prefab)
    chargeBullet: Prefab;

    // @property(JsonAsset)
    // navJson: JsonAsset;

    // zone: string;

    targetPos: Vec3;
    moveState = AIMoveState.Idle;
    actionState = AIActionState.Idle;
    enemyTarget: GamerController;
    allEnemyTargets: GamerController[] = [];

    /* 可否移动 */
    private move = false;
    /* 路线 */
    private _path: Vec3[] = [];
    /* 欧拉角度Y */
    private roleAngel = 0;
    private step = 0;
    private currstep = null;
    private pos: Vec3 = new Vec3();
    private des: Vec3 = new Vec3();

    hp: number = 10000;
    public get Hp(): number { return this.hp; }
    public set Hp(value: number) {
        if (this.isDead) return;
        this.hp = clamp(value, 0, 10000);
        ControlUI.Instance.bossHp.fillRange = this.hp / 10000;
        if (this.hp == 0) this.Die();
    }

    patrol = 0;
    minigunTime = 0.2;
    minigunCD = 3;
    minigunMax = 50;
    minigunNum = 0;
    chargeTime = 10;
    chargeAim = 1;
    chargeCD = 2;
    canMini = true;
    canCharge = true;
    canChargeShoot = false;
    chargeProcess = 0;
    isInit: boolean = true;
    mini: Gun;
    charge: Gun;

    protected onLoad(): void {
        this.animationController = this.node.children[0].getComponent(animation.AnimationController);
        this.characterController = this.getComponent(CapsuleCharacterController);
        // this.characterController.on('onControllerColliderHit', this.onControllerColliderHit, this);
        this.core = this.node.children[1];
        let collider = this.core.getComponent(Collider);
        collider.on('onControllerTriggerEnter', this.onControllerTriggerEnter, this);
        collider.on('onControllerTriggerExit', this.onControllerTriggerExit, this);
        let triggers = this.node.getComponentsInChildren(BodyTrigger);
        triggers.forEach((value) => { value.controller = this; });
        this.mini = new Gun;
        this.mini.damage = 20;
        this.mini.headShotMultiply = 1;
        this.charge = new Gun;
        this.charge.damage = 200;
        this.charge.headShotMultiply = 1;
    }

    start() {
        this.type = 'RobotController';
        this.Patrol();
        //解析json信息
        // let zoneNodes = Builder.buildZoneByJson(this.navJson.json);
        // //区域名字直接使用json文件的名字
        // this.zone = this.navJson.name;
        // NavMesh.setZoneData(this.zone, zoneNodes);
        // this.enemyTarget = PlayerController.Instance;
    }

    update(deltaTime: number) {
        this.CheckUI();
        if (GameUI.Instance.isFinish && this.moveState != AIMoveState.Idle) return this.StateSwitch(AIMoveState.Idle);
        this.CheckMove(deltaTime);
        this.CheckDir();
        this.Move(deltaTime); // 调整deltaTime以补偿跳过的帧
        if (this.enemyTarget?.core) this.core.lookAt(this.enemyTarget.core.getWorldPosition(), this.core.up);
        this.ShootTarget();
    }

    CheckUI() {
        let oPos = this.core.getWorldPosition();
        let dPos = ControlUI.Instance.playerCamera.node.getWorldPosition();
        let delta = oPos.clone().subtract(dPos);
        let camera = ControlUI.Instance.playerCamera;
        let euler = toDegree(Vec3.angle(delta, camera.node.forward));
        let ray = new geometry.Ray;
        geometry.Ray.fromPoints(ray, oPos, dPos);
        let length = Vec3.distance(oPos, dPos);
        ControlUI.Instance.bossStateBar.active = camera.fov > euler && !PhysicsSystem.instance.raycastClosest(ray, 1 << 0, length, false);
    }

    CheckMove(deltaTime: number): void {
        if (this.isDead) return;
        if (!this.isInit) return;
        if (GameUI.Instance.isFinish) return;
        if (this.move) {
            //播放动画
            this.pos.set(this.node.position)
            if (this.currstep != this.step) {
                this.des.set(this._path[this.step]);
                this.currstep = this.step;
            }
            if (!this.enemyTarget) this.rotateRole(this.pos, this.des);
            if (nUtils.CheckArrived(this.pos, this.des)) this.changeStep();
        }
        if (this.enemyTarget) this.rotateRole(this.pos, this.enemyTarget.node.getWorldPosition());
    }

    CheckDir() {
        if (this.isDead) return;
        if (!this.isInit) return;
        if (!this.move) return;
        let desDir = v3();
        Vec3.subtract(desDir, this.des, this.node.getWorldPosition());
        desDir.y = 0;
        desDir.normalize();
        let angle = this.node.eulerAngles.y;
        angle = toRadian(angle);
        let dir = v3();
        Vec3.rotateY(dir, desDir, Vec3.ZERO, -angle);
        dir = dir.normalize();
        this.Control_x = dir.x;
        this.Control_z = -dir.z;
        this.deltaNor.x = dir.x;
        this.deltaNor.y = -dir.z;
    }

    /**
     * @Date: 2022-02-1 01:26:04
     * @LastEditors: iwae
     * @description: 水平方向面对目标
     * @param {Vec3} pos 转向节点位置
     * @param {Vec3} target 目标位置
     */
    rotateRole(pos: Vec3, target: Vec3): void {
        this.roleAngel = (Math.atan2(target.x - pos.x, target.z - pos.z) * (180 / Math.PI) + 180) % 360;
        //角色转动的角度,相对Z轴，逆时针为正方向
        this.node.setRotationFromEuler(0, this.roleAngel);
    }

    changeStep(): void {
        this.step++
        if (this.step == this._path.length) {
            //站立
            this.patrol++;
            if (this.patrol == this.patrolPoints.children.length) this.patrol = 0;
            this.deltaNor.x = 0;
            this.deltaNor.y = 0;
            this.Control_x = 0;
            this.Control_z = 0;
            this.move = false;
            this.CheckMoveState();
        }
    }

    startMove(path): void {
        this.stopMove();
        this._path = path;
        this.move = true;
    }

    stopMove(): void {
        this.move = false;
        this._path.length = 0;
        this.step = 0;
        this.Control_x = 0;
        this.Control_z = 0;
        this.currstep = null;

    }

    CheckMoveState() {
        this.StateSwitch(AIMoveState.Idle);
        this.scheduleOnce(() => { this.StateSwitch(AIMoveState.Patrol) }, 3);
    }

    StateSwitch(state?: AIMoveState) {
        if (state != undefined) this.moveState = state;
        switch (this.moveState) {
            case AIMoveState.Idle:
            case AIMoveState.Die:
                this.stopMove();
                break;
            //禁用找箱子
            // case AIMoveState.Search:
            //     if (!InGameManager.Instance.moveAgent(this, this.boxTarget.getWorldPosition(pos))) this.StateSwitch(AIMoveState.Patrol);
            //     break;
            // case AIMoveState.NextPoison:
            //     PoisonManager.Instance.nextPoison.getWorldPosition(pos);
            //     let pos2 = pos.clone();
            //     pos2.y = 100;
            //     let ray = new geometry.Ray;
            //     geometry.Ray.fromPoints(ray, pos2, pos);
            //     if (PhysicsSystem.instance.raycastClosest(ray, 1 << 0, 100, false)) {
            //         const result = PhysicsSystem.instance.raycastClosestResult;
            //         pos = result.hitPoint;
            //     }
            //     if (!InGameManager.Instance.moveAgent(this, pos)) {
            //         this.failNextPoison = true;
            //         this.CheckMoveState();
            //     }
            //     break;
            case AIMoveState.Patrol:
                this.Patrol();
                break;
        }
    }

    Patrol() {
        if (this.isDead) return;
        if (!this.isInit) return;
        if (GameUI.Instance.isFinish) return;
        let point = this.patrolPoints.children[this.patrol];
        this.startMove([point]);
    }

    ActionSwitch(state?: AIActionState) {
        if (state != undefined) this.actionState = state;
        switch (this.actionState) {
            case AIActionState.Idle:
            case AIActionState.Die:
                break;
            case AIActionState.Atk:
                break;
        }
        this.ReadyShoot();
    }

    ReadyShoot() {
        if (!this.animationController) return;
        if (this.isDead) return;
        if (!this.isInit) return;
        if (GameUI.Instance.isFinish) return;
        let isShoot = this.animationController.getValue("IsShoot");
        if (!this.enemyTarget || this.actionState != AIActionState.Atk) {
            if (isShoot) this.ShootEnd();
            return;
        }
        let ray = new geometry.Ray;
        geometry.Ray.fromPoints(ray, this.core.getWorldPosition(), this.enemyTarget.core.getWorldPosition());
        if (PhysicsSystem.instance.raycastClosest(ray, 1 << 0 | 1 << 2 | 1 << 5, 100)) {
            const result = PhysicsSystem.instance.raycastClosestResult;
            const collider = result.collider;
            const body = collider.getComponent(BodyTrigger);
            if (body) { if (!isShoot) this.ShootStart(); }
            else { if (isShoot) this.ShootEnd(); }
        }
        else { if (isShoot) this.ShootEnd(); }
    }

    Die(): void {
        this.ShootEnd();
        super.Die();
        this.StateSwitch(AIMoveState.Die);
        this.ActionSwitch(AIActionState.Die);
        this.ShootEnd();
        this.move = false;
        this.unscheduleAllCallbacks();
        this.scheduleOnce(() => {
            this.isInit = false;
            PoolManager.PutNode(this.node);
        }, 2);
    }

    CheckTarget() {
        if (this.isDead) return;
        if (!this.isInit) return;
        if (GameUI.Instance.isFinish) return;
        let ray = new geometry.Ray;
        if (this.enemyTarget) {
            if (this.enemyTarget.Hp == 0) {
                let num = this.allEnemyTargets.indexOf(this.enemyTarget);
                if (num != -1) this.allEnemyTargets.splice(num, 1);
                this.enemyTarget = null;
            }
            else {
                let oPos = this.enemyTarget.core.getWorldPosition();
                let dPos = this.core.getWorldPosition();
                let dis = Vec3.distance(oPos, dPos);
                geometry.Ray.fromPoints(ray, oPos, dPos);
                if (PhysicsSystem.instance.raycastClosest(ray, 1 << 0, dis, false)) this.enemyTarget = null;
            }
        }
        if (!this.enemyTarget) {
            this.allEnemyTargets = this.allEnemyTargets.filter((value, index, array) => { if (value.Hp > 0) return value; });
            if (this.allEnemyTargets.length > 0) {
                if (this.allEnemyTargets.length > 1) this.allEnemyTargets = this.allEnemyTargets.sort((a, b) => { return Vec3.distance(a.core.getWorldPosition(), this.core.getWorldPosition()) - Vec3.distance(b.core.getWorldPosition(), this.core.getWorldPosition()); });
                for (let i = 0; i < this.allEnemyTargets.length; i++) {
                    const element = this.allEnemyTargets[i];
                    geometry.Ray.fromPoints(ray, this.core.getWorldPosition(), element.core.getWorldPosition());
                    if (PhysicsSystem.instance.raycastClosest(ray, 1 << 0 | 1 << 2 | 1 << 5, 100, true)) {
                        const result = PhysicsSystem.instance.raycastClosestResult;
                        const target = result.collider.getComponent(BodyTrigger);
                        if (target && target.controller == element) {
                            this.enemyTarget = element;
                            break;
                        }
                    }
                }
            }
        }
        this.ActionSwitch(this.enemyTarget ? AIActionState.Atk : AIActionState.Idle);
    }

    EnemyCheck() {
        if (this.allEnemyTargets.indexOf(this.enemyTarget) == -1) this.enemyTarget == null;
    }

    ShootTarget() {
        if (this.isDead) return;
        if (!this.isInit) return;
        if (GameUI.Instance.isFinish) return;
        if (this.enemyTarget) {
            if (this.canMini) {
                let ePos = this.enemyTarget.core.getWorldPosition();
                let cPos = this.core.getWorldPosition();
                let delta = ePos.clone().subtract(cPos);
                delta.y = 0;
                let forward = this.core.forward;
                forward.y = 0;
                if (toDegree(Vec3.angle(delta, forward)) < 30) {
                    let ray = new geometry.Ray;
                    geometry.Ray.fromPoints(ray, ePos, cPos);
                    let dis = Vec3.distance(ePos, cPos);
                    if (!PhysicsSystem.instance.raycastClosest(ray, 1 << 0, dis, false)) {
                        this.ShootBullet();
                    }
                }
            }
            if (this.canCharge) {
                if (this.chargeProcess == 0) this.Charge();
                else if (this.chargeProcess == 10) {
                    if (!this.canChargeShoot) this.ChargeShoot();
                    else this.ChargeBullet();
                }
            }
        }
    }

    MinigunRate() {
        this.canMini = true;
    }

    MinigunCD() {
        this.canMini = true;
        let particles = this.minigunFire.getComponentsInChildren(ParticleSystem);
        particles.forEach(element => { element.play(); });
    }

    ShootBullet(): void {
        if (this.isDead) return;
        if (!this.isInit) return;
        if (GameUI.Instance.isFinish) return;
        this.curSpread = 1;
        this.minigun.lookAt(this.enemyTarget.core.getWorldPosition(), this.minigun.up);
        let oPos = this.minigun.getWorldPosition();
        let forward = this.minigun.forward.clone().normalize();
        let up = this.minigun.up.clone().normalize();
        let right = this.minigun.right.clone().normalize();
        let dir = this.getRandomShootDirectionSimple(forward, up, right);
        let dPos = v3();
        Vec3.add(dPos, oPos, dir.multiplyScalar(1000));
        let ray = new geometry.Ray();
        geometry.Ray.fromPoints(ray, oPos, dPos);
        if (PhysicsSystem.instance.raycastClosest(ray, 1 << 0 | 1 << 2 | 1 << 5, 1000)) {
            const result = PhysicsSystem.instance.raycastClosestResult;
            const point = result.hitPoint.clone();
            InGameManager.Instance.ShowTrail(oPos, point, this.bullet);
            const rig = result.collider.attachedRigidBody;
            if (rig && (rig.group == 1 << 2 || rig.group == 1 << 5)) {
                rig.getComponent(BodyTrigger).Damage(this.mini, this);
                InGameManager.Instance.ShowBlood(point);
            }
        }
        else InGameManager.Instance.ShowTrail(oPos, dPos, this.bullet);
        let effect: Node = PoolManager.GetNodeByPrefab(this.shootParticle, InGameManager.Instance.effects);
        effect.setWorldPosition(this.minigun.getWorldPosition());
        effect.setWorldRotation(this.node.children[0].getWorldRotation());
        let particles = effect.getComponentsInChildren(ParticleSystem);
        particles.forEach((element) => { element.stop(); element.play(); });
        this.scheduleOnce(() => { PoolManager.PutNode(effect); }, 1);
        let vol = Math.max(1 - (Vec3.distance(this.minigun.getWorldPosition(), InGameManager.Instance.cameraNode.getWorldPosition()) / 300), 0);
        AudioManager.Instance.PlayShoot(8, vol);

        this.canMini = false;
        this.minigunNum++;
        if (this.minigunNum == this.minigunMax) {
            this.minigunNum = 0;
            let particles = this.minigunFire.getComponentsInChildren(ParticleSystem);
            particles.forEach(element => { element.stop(); });
            this.scheduleOnce(this.MinigunCD, this.minigunCD);
        }
        else this.scheduleOnce(this.MinigunRate, this.minigunTime);
    }

    Charge() {
        let particles = this.chargeParticle.getComponentsInChildren(ParticleSystem);
        particles.forEach(element => { element.play(); });
        let ball = this.chargeParticle.children[3];
        ball.setScale(Vec3.ZERO);
        tween(ball)
            .to(10, { scale: Vec3.ONE.clone().multiplyScalar(2) })
            .start();
        this.schedule(this.ChargeCount, 1, 9);
    }

    ChargeCount() {
        this.chargeProcess++;
    }

    ChargeShoot() {
        this.eye.play();
        this.scheduleOnce(this.ChargeShootReady, 1);
    }

    ChargeShootReady() {
        this.canChargeShoot = true;
    }

    ChargeBullet() {
        this.chargeProcess = 0;
        this.canCharge = false;
        this.canChargeShoot = false;
        let particles = this.chargeParticle.getComponentsInChildren(ParticleSystem);
        particles.forEach(element => { element.stop(); });
        this.scheduleOnce(this.ChargeReady, this.chargeCD);

        this.chargeGun.lookAt(this.enemyTarget.core.getWorldPosition(), this.chargeGun.up);
        let oPos = this.chargeGun.getWorldPosition();
        let forward = this.chargeGun.forward.clone().normalize();
        let dPos = v3();
        Vec3.add(dPos, oPos, forward.multiplyScalar(1000));
        let ray = new geometry.Ray();
        geometry.Ray.fromPoints(ray, oPos, dPos);
        if (PhysicsSystem.instance.raycastClosest(ray, 1 << 0 | 1 << 2 | 1 << 5, 1000)) {
            const result = PhysicsSystem.instance.raycastClosestResult;
            const point = result.hitPoint.clone();
            InGameManager.Instance.ShowTrail(oPos, point, this.chargeBullet);
            const rig = result.collider.attachedRigidBody;
            if (rig && (rig.group == 1 << 2 || rig.group == 1 << 5)) {
                rig.getComponent(BodyTrigger).Damage(this.charge, this);
                InGameManager.Instance.ShowBlood(point);
            }
        }
        else InGameManager.Instance.ShowTrail(oPos, dPos, this.chargeBullet);
        let effect: Node = PoolManager.GetNodeByPrefab(this.shootParticle2, InGameManager.Instance.effects);
        effect.setWorldPosition(this.chargeGun.getWorldPosition());
        effect.setWorldRotation(this.node.children[0].getWorldRotation());
        particles = effect.getComponentsInChildren(ParticleSystem);
        particles.forEach((element) => { element.stop(); element.play(); });
        this.scheduleOnce(() => { PoolManager.PutNode(effect); }, 1);
        let vol = Math.max(1 - (Vec3.distance(this.chargeGun.getWorldPosition(), InGameManager.Instance.cameraNode.getWorldPosition()) / 500), 0);
        AudioManager.Instance.PlayShoot(9, vol);
        // InGameManager.Instance.ShowTrail(oPos, dPos, this.chargeBullet, this.ChargeBurst.bind(this));
    }

    // ChargeBurst() {

    // }

    ChargeReady() {
        this.canCharge = true;
    }

    Damage(part: BodyTrigger, weapon: Weapon, source: GamerController): void {
        if (source == this) return;
        let damage = weapon.damage;
        this.Hp -= damage;
        if (source.type == 'PlayerController') GameUI.Instance.ShowDmg(damage, part.node.getWorldPosition());
        let pos = this.node.getWorldPosition();
        let dis = Vec3.distance(pos, source.node.getWorldPosition(pos));
        AudioManager.Instance.PlayMetalHurt(Math.max(1 - dis / 100, 0));
        let num = this.allEnemyTargets.indexOf(source);
        if (num == -1) this.allEnemyTargets.push(source);
        if (!this.enemyTarget) this.enemyTarget = source;
    }

    onControllerTriggerEnter(event: CharacterControllerContact) {
        const other = event.characterController;
        if (other == this.characterController) return;
        if (other.group == 1 << 1 && this.allEnemyTargets.indexOf(other.getComponent(GamerController)) == -1) {
            this.allEnemyTargets.push(other.getComponent(GamerController));
            this.CheckTarget();
            if (other.getComponent(GamerController) == this.enemyTarget) this.unschedule(this.EnemyCheck);
        }
    }

    onControllerTriggerExit(event: CharacterControllerContact) {
        const other = event.characterController;
        let num = this.allEnemyTargets.indexOf(other.getComponent(GamerController));
        if (other.group == 1 << 1 && num != -1) {
            this.allEnemyTargets.splice(num, 1);
            if (this.enemyTarget == other.getComponent(GamerController)) this.scheduleOnce(this.EnemyCheck, 3);
        }
    }

}

enum AIMoveState {
    Idle,
    Patrol,
    Die
}

enum AIActionState {
    Idle,
    Atk,
    Die
}
